package com.spring.nio.core.context;

import com.alibaba.fastjson.JSONObject;
import com.spring.nio.core.BasicServer;
import com.spring.nio.core.http.HttpContext;
import com.spring.nio.core.http.HttpResponse;
import com.spring.nio.util.LocalTime;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.charset.Charset;
import java.time.LocalDateTime;
import java.util.Iterator;
import java.util.Map;
import java.util.Optional;

/**
 * @Auther: YSFY
 * @Date: 2020-11-14 17:09
 * @Pacage_name: com.alibaba.server.nio.core
 * @Project_Name: net-server
 * @Description: 服务上下文管理器
 */

@Slf4j
@SuppressWarnings("all")
public class NioServerContext {

    private static final Object lock = new Object();

    /**
     * 创建Selector
     * @return selector
     * */
    public static Selector openSelector() throws IOException {
        return Selector.open();
    }

    /**
     * 根据key获取配置文件数据
     * @param param
     * @return object
     * */
    public static String getValue(String param) {
        Map<String, Object> cacheMap = BasicServer.getMap();
        if(!cacheMap.isEmpty() && cacheMap.containsKey(param)) {
            return cacheMap.get(param).toString();
        }

        return "";
    }

    /**
     * 获取当前请求的服务端端口号，根据端口号判断具体属于哪类请求
     *
     * @param selectionKey
     * @return
     * @throws IOException
     */
    public static String getPort(SelectionKey selectionKey) throws IOException {
        return String.valueOf(((InetSocketAddress) ((ServerSocketChannel) selectionKey.channel()).getLocalAddress()).getPort());
    }

    /**
     * 根据SocketChannel获取远程连接信息(ip:port)
     * @param socketChannel
     * @return
     * */
    public static String getRemoteAddress(SocketChannel socketChannel) {
        String ip = "";
        Integer port = 0;
        try {
            if(socketChannel == null) {
                return "";
            }
            synchronized (lock) {
                ip = ((InetSocketAddress) socketChannel.getRemoteAddress()).getAddress().getHostAddress();
                port = ((InetSocketAddress) socketChannel.getRemoteAddress()).getPort();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return (ip + ":" + port);
    }

    /**
     * 根据SocketChannel获取本地连接信息(ip:port)
     * @param socketChannel
     * @return
     * */
    public static String getLocalAddress(SocketChannel socketChannel) {
        String ip = "";
        Integer port = 0;
        try {
            ip = ((InetSocketAddress) socketChannel.getLocalAddress()).getAddress().getHostAddress();
            port = ((InetSocketAddress) socketChannel.getLocalAddress()).getPort();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return (ip + ":" + port);
    }

    /**
     * 获取服务端本地地址
     * @param socketChannel
     * @return
     */
    public static String getServerLocalAddress(ServerSocketChannel serverSocketChannel) {
        String ip = "";
        Integer port = 0;
        try {
            ip = ((InetSocketAddress) serverSocketChannel.getLocalAddress()).getAddress().getHostAddress();
            port = ((InetSocketAddress) serverSocketChannel.getLocalAddress()).getPort();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return (ip + ":" + port);
    }

    /**
     * Selector 可选事件注册
     * @param socketChannel
     * @param selector
     * @param opt
     * @return
     * @throws IOException
     * */
    public static SelectionKey EventRegister(SocketChannel socketChannel, Selector selector, final int opt) throws IOException {
        if(!selector.isOpen()) {
            log.error("[ " + LocalTime.formatDate(LocalDateTime.now()) + " ] NioServerContext | --> Selector is not open yet, threadName = {}", Thread.currentThread().getName());
            return null;
        }

        socketChannel.configureBlocking(false);
        selector.wakeup();
        return socketChannel.register(selector, opt);
    }

    /**
     * 关闭socketChannel
     * @param socketChannel
     * @param subReactor
     * @return Boolean 关闭结果
     * */
    public static void closedAndRelease(SocketChannel socketChannel) {
        String remoteAddress = "";
        try {
            if(Optional.ofNullable(socketChannel).isPresent()) {
                remoteAddress = NioServerContext.getRemoteAddress(socketChannel);
                String localAddress = NioServerContext.getLocalAddress(socketChannel);

                // 1、关闭socketChannel，将会发送流截至符 -1到客户端
                Socket socket = socketChannel.socket();
                if(!socket.isClosed()) {
                    socketChannel.shutdownInput();
                    socketChannel.shutdownOutput();
                    socketChannel.close();
                }
                log.warn("[ " + LocalTime.formatDate(LocalDateTime.now()) + " ] NioServerContext | --> socketChannel closed local connected success, remoteAddress = {}, localAddress = {}, thread = {}",
                    remoteAddress, localAddress, Thread.currentThread().getName());
            }
        } catch (Exception e) {
            if(e instanceof ClosedChannelException) {
                log.warn("[ " + LocalTime.formatDate(LocalDateTime.now()) + " ] NioServerContext | --> socketChannel shutdown input or output warning, channel is closed, address = {}, message = {}",
                    NioServerContext.getLocalAddress(socketChannel), e.getMessage());
            }

            if(e instanceof NotYetConnectedException) {
                log.warn("[ " + LocalTime.formatDate(LocalDateTime.now()) + " ] NioServerContext | --> socketChannel is not connect yet, address = {}, message = {}",
                    NioServerContext.getLocalAddress(socketChannel), e.getMessage());
            }

            e.printStackTrace();
        } finally {
        }
    }

    /**
     * 指定通道发送数据
     *
     * */
    public static void sendMessage(SocketChannel socketChannel, HttpResponse httpResponse) throws IOException {
        StringBuffer sendMessage = new StringBuffer("");
        if(socketChannel.isConnected()) {
            sendMessage.append(httpResponse.getRequestHeader()).append("\r\n");
            HttpContext.HeaderEntry headerEntry = httpResponse.getHeaderEntry();
            Iterator iterator = headerEntry.getMap().entrySet().iterator();
            while (iterator.hasNext()) {
                Map.Entry<String, Object> entry = (Map.Entry<String, Object>)iterator.next();
                sendMessage.append(entry.getKey()).append(": ").append(entry.getValue().toString()).append("\r\n");
            }

            sendMessage.append("").append("\r\n");
            sendMessage.append(httpResponse.getBodyData());
        }

        socketChannel.write(ByteBuffer.wrap(sendMessage.toString().getBytes(Charset.forName("utf-8"))));
    }
}
